/*
 * Example usage of the typesafe enum feature in cxxomfort.
 *
 * This code showcases usage of typesafe enum by declaring
 * two enum types: "fish" and "colour", that both have
 * semantics for taking the value "salmon".
 * 
 * As per a normal enum, the following operations should 
 * *not* be allowed (they are commented out and 
 * de-commenting them should fail to compile), 
 * given typesafe enum types E1, E2, ... and integer type X:
 * 
 * * enum E1 e = x; // initialize directly from an integral
 * * enum E2 e2 = e1; // initialize from an enum of another type
 * * e1 == e2; // comparison between enums of different types
 * * int y = e1; // implicit convert enum to integer
 * 
 *
 * This and other examples assume cxxomfort is installed in
 * include library path; if otherwise, adjust with
 * -I /path/to/cxxomfort .
 */

#include <cxxomfort/base.hpp>
#include <cxxomfort/library/typesafe_enum.hpp>
#include <cxxomfort/library/type_name.hpp>
#include <iostream>
#include <cassert>

// fish are a kind of thing
struct fish_def { enum type { tuna=4, salmon, clown, swordfish }; };
typedef cxxomfort::library::typesafe_enum<fish_def, short> fish_t;

// colours are another kind of thing
struct colour_def { enum type { red, green, blue, purple, salmon, white, gray, black }; };
typedef cxxomfort::library::typesafe_enum<colour_def, unsigned int> colour_t;

struct enum_def { enum type { VAL1=1, VAL2, VAL3, VAL4 }; };
typedef cxxomfort::library::typesafe_enum<enum_def> enum_t;

template <typename Enum> void show_enum_info (Enum);

int main () {
    using namespace std;
    cxxomfort::output_info(stdout);cout<< endl;
    show_enum_info(colour_t());
    show_enum_info(fish_t());
    show_enum_info(enum_t());
	show_enum_info(enum_t(enum_t::VAL2));

#if 1
    //
    // testing constructubility rules from eg.: ints
    //

    //enum_t etest0; // should it compile? (enum class test says it should)
    //enum_t etest1(11); // NO (Eshort1 test says it shouldn't)
    //(void)etest1;
    //enum_t etestfish = fish_t::clown; // NO
    //(void)etestfish;
    //enum_t e5 = -15; // should NOT compile
    //(void)e5;

#endif

#if 1 // test with c++11 enum class for the above
#if (CXXOMFORT_CXX_STD>=2011)
    enum class enumclass : short { tuna, salmon, clown, swordfish };
    enum class enum2 : short { aa, bb, cc} ;

    enumclass E0; (void)E0; // YES
    //enumclass E1 (4);  // NO: 4 != short
    //enumclass Eshort1 ((short)3); (void)Eshort1; // NO, but is our only access point in typesafe_enum
    // enumclass Ishort1 = {4}; (void)Ishort1; // NO; maybe until C++17
    //enumclass E1(4); (void)E1; // NO; conversion
    //enumclass Eshort2 = (short)-4; (void)Eshort2; // NO; conversion (even if better than the above)

    enumclass Efi1 = enumclass::swordfish; (void)Efi1; // YES (of course)
    //enumclass Efi2 = enum2::bb; (void)Efi2; // NO (of course)

    enumclass Econv1 = static_cast<enumclass>(4); (void)Econv1; // YES, since it's a C++11 addition
    enumclass EconvE = explicit_cast<enumclass>(4); (void)EconvE; // YES in C++11; we want to also do this in C++03
    enumclass Econv2 = (enumclass)4; (void)Econv2; // This may or may not compile.
#endif
#endif

#if 0
    colour_t c(colour_t::red);

    // we can have certain kinds of fish
    fish_t fi1 = fish_t::swordfish;
    fish_t fi2 = fish_t::salmon;

    // we can have certain kinds of colour
    colour_t co1 = colour_t::purple;
    colour_t co2 = colour_t::salmon;

    // would we need to, we can categorize our kinds of fish
    //int ifi2 = fi2.underlying();
    //int ifi3 = explicit_cast<short>(fi2);
    //cout<< "int from fish_t:fi2 : "<< ifi2<< "\n";
    //cout<< "int from fish_t:fi2 : "<< ifi3<< "\n";

    // fish are not numbers however; they have personality!
    // for example, we declared up there that our fish are short
    // how can we describe that?
    //fish_t fish_sh ((short)3); // NO; this does not work for native enums
    // fish_t fish_lo ((long)3); // NO; this does not work for native enums
    //fish_t fishr2 = static_cast<fish_t>(2);  // works with C++11 enum class, but can't work with native
    // (void)fishr2;
    //fish_t fishr3 = explicit_cast<fish_t>(2); // should work in C++03 and C++11.
    //(void)fishr3;

    //fish_t fi3 = cxxomfort::library::from_underlying<fish_t>((long)2);
    //(void)fi3;

    // but they are not long
    // fish_t filon ((long)3);  // does not compile
    // of course, in the end the one truth is that fish are fish
    // fish_t fishz1 = enum_cast<fish_t>(4); // should compile
    // but we can be more explicit about that
    // fish_t fishz2 = explicit_cast<fish_t>(4);
    // cout<< "fishz1 "<< (fishz1).value()<< " fishz2 "<< (fishz2).value()<< endl;

    //assert (fishz1==fishz2);

    // fish are comparable to each other, same with colours
    assert (false==(fi1==fi2));
    assert (false==(co1==co2));
    // but we can't really say a fish is like a colour
    //assert (false==(fi2==co1)); // this should not compile

    //colour_t conum1 = 4; // should not compile
    // colour_t conum2 = (colour_t)4u; (void)conum2; // we want this to compile if possible, as it does for enum class
    //colour_t conum3 = explicit_cast<colour_t>(4); (void)conum3; // we want this to compile, do we need to add a constructor?
    //colour_t conum5 = static_cast<colour_t>(4u); (void)conum5;

#endif

    /**
     * The following section, if enabled, must NOT compile
     */
#if 0
    fish_t xi1 = 14;  (void)xi1; // a number is not a fish
    int ixi1 = (static_cast<int>(xi1)); (void)ixi1; // and a fish is not a number
    fish_t notsalmon = colour_t::salmon; (void)notsalmon; // a colour is not a fish.
    colour_t nottunashade = fish_t::tuna; (void)nottunashade; // a fish is not a colour.
    fish_t notnumber = (unsigned short)4; (void)notnumber;

    bool bi= (co1==fi3); (void)bi; // a fish is not like a colour

#endif

}


template <typename Enum> 
void show_enum_info (Enum) {
	using namespace std;
    using cxxomfort::library::type_name;
    typedef typename std::underlying_type<Enum>::type U;
    cout<< "enum is: "<< type_name<Enum>()<< endl;
    cout<< "  enum "<< type_name<typename Enum::enum_type>()<< "\n";
    cout<< " inner "<< type_name<typename Enum::inner_type>()<< "\n";
    cout<< " under "<< type_name<U>()<< endl;
	
}
